﻿using System;
using System.Collections.Generic;
using System.Data;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Nvigorate.Data;
using Nvigorate.Test.Comparer;
using Nvigorate.Test.Data.Doubles;

namespace Nvigorate.Test.Data
{
    /// <summary>
    /// Summary description for DataCommandTests
    /// </summary>
    [TestClass]
    public class DataCommandTests : DataTestBase
    {
        #region ExecuteCommand Tests

        [TestMethod]
        public void ExecuteCommand_InvokesFunction()
        {
            // Arrange
            var commandText = "DELETE FROM Employee";
            var connection = CreateConnection();
            var provider = CreateProvider(commandText, connection.Object);
            var command = new TestDbCommand(commandText) {ResultCount = 5};

            // Act
            var dataCommand = new DatabaseCommand(command, provider.Object);
            var actual = dataCommand.ExecuteCommand(connection.Object, false, c => c.ExecuteNonQuery());

            // Assert
            Assert.AreEqual(5, actual);
        }

        [TestMethod]
        public void ExecuteCommand_UseTransaction_CallsBeginAndCompleteTransaction()
        {
            // Arrange
            var commandText = "DELETE FROM Employee";
            var connection = CreateConnection();
            var provider = CreateProvider(commandText, connection.Object);
            var transactionManager = new TestTransactionManager();

            // Act
            var dataCommand = new DatabaseCommand(new TestDbCommand(commandText), provider.Object, transactionManager,
                                                  new IDbDataParameter[] {});
            dataCommand.ExecuteCommand(connection.Object, true, c => c.ExecuteNonQuery());

            // Assert
            Assert.AreEqual(1, transactionManager.BeginCalledCount);
            Assert.AreEqual(1, transactionManager.CompleteCalledCount);
            Assert.AreEqual(0, transactionManager.AbortCalledCount);
        }

        [TestMethod]
        public void ExecuteCommand_NoTransaction_DoesNotCallTransactionManagerMethods()
        {
            // Arrange
            var commandText = "DELETE FROM Employee";
            var connection = CreateConnection();
            var provider = CreateProvider(commandText, connection.Object);
            var transactionManager = new TestTransactionManager();

            // Act
            var dataCommand = new DatabaseCommand(new TestDbCommand(commandText), provider.Object, transactionManager,
                                                  new IDbDataParameter[] {});
            dataCommand.ExecuteCommand(connection.Object, false, c => c.ExecuteNonQuery());

            // Assert
            Assert.AreEqual(0, transactionManager.BeginCalledCount);
            Assert.AreEqual(0, transactionManager.CompleteCalledCount);
            Assert.AreEqual(0, transactionManager.AbortCalledCount);
        }

        [TestMethod, ExpectedException(typeof (DataInterfaceException))]
        public void ExecuteCommand_HasException_ThrowsException()
        {
            // Arrange
            var commandText = "DELETE FROM Employee";
            var connection = CreateConnection();
            var provider = CreateProvider(commandText, connection.Object);
            var transactionManager = new TestTransactionManager();

            // Act
            var dataCommand = new DatabaseCommand(new TestDbCommand(commandText), provider.Object, transactionManager,
                                                  new IDbDataParameter[] {});
            dataCommand.ExecuteCommand<int>(connection.Object, true, c => { throw new Exception("Test exception"); });
        }

        [TestMethod]
        public void ExecuteCommand_HasException_CallsAbort()
        {
            // Arrange
            var commandText = "DELETE FROM Employee";
            var connection = CreateConnection();
            var provider = CreateProvider(commandText, connection.Object);
            var transactionManager = new TestTransactionManager();

            // Act
            var dataCommand = new DatabaseCommand(new TestDbCommand(commandText), provider.Object, transactionManager,
                                                  new IDbDataParameter[] {});
            try
            {
                dataCommand.ExecuteCommand<int>(connection.Object, true, c => { throw new Exception("Test exception"); });
            }
// ReSharper disable EmptyGeneralCatchClause
            catch
// ReSharper restore EmptyGeneralCatchClause
            {
            }

            // Assert
            Assert.AreEqual(1, transactionManager.BeginCalledCount);
            Assert.AreEqual(0, transactionManager.CompleteCalledCount);
            Assert.AreEqual(1, transactionManager.AbortCalledCount);
        }

        #endregion

        #region Execute Tests

        [TestMethod]
        public void Execute_CallsExecuteNonQuery()
        {
            // Arrange
            var commandText = "DELETE FROM Employee";
            var connection = CreateConnection();
            var provider = CreateProvider(commandText, connection.Object);
            var dbCommand = new TestDbCommand(commandText) {ResultCount = 5};

            // Act
            var dataCommand = new DatabaseCommand(dbCommand, provider.Object);
            var actual = dataCommand.Execute(connection.Object, false);

            // Assert
            Assert.AreEqual(5, actual);
            Assert.AreEqual(1, dbCommand.ExecuteNonQueryCount);
        }

        #endregion

        #region GetScalar Tests

        [TestMethod]
        public void GetScalar_CallsExecuteScalar()
        {
            // Arrange
            var commandText = "DELETE FROM Employee";
            var connection = CreateConnection();
            var provider = CreateProvider(commandText, connection.Object);
            var dbCommand = new TestDbCommand(commandText) {ResultCount = 5};

            // Act
            var dataCommand = new DatabaseCommand(dbCommand, provider.Object);
            dataCommand.GetScalar(connection.Object);

            // Assert
            Assert.AreEqual(5, dbCommand.ResultCount);
            Assert.AreEqual(1, dbCommand.ExecuteScalarCount);
        }

        #endregion

        #region GetDataTable Tests

        [TestMethod]
        public void GetTable_ReturnsTableFromDbCommand()
        {
            // Arrange
            var commandText = "SELECT * FROM Employee";
            var connection = CreateConnection();
            var provider = CreateProvider(commandText, connection.Object);
            var dt = GetTable("Employee");
            var dbCommand = new TestDbCommand(commandText) {ResultTable = dt};

            // Act
            var dataCommand = new DatabaseCommand(dbCommand, provider.Object);
            var actual = dataCommand.GetDataTable(connection.Object, "Employee");

            // Assert
            var comparer = new DataTableEqualityComparer();
            Assert.IsTrue(comparer.Equals(dt, actual));
        }

        #endregion

        #region GetDataSet Tests

        [TestMethod]
        public void GetDataSet_ReturnsDataSetFromDbCommand()
        {
            // Arrange
            var commandText = "SELECT * FROM Employee";
            var connection = CreateConnection();
            var provider = CreateProvider(commandText, connection.Object);
            string[] tables = {"Employee", "Job"};
            var dataSetName = "EmployeeSet";
            var ds = GetEmployeeSet(dataSetName, tables);
            var dbCommand = new TestDbCommand(commandText) {ResultSet = ds};

            // Act
            var dataCommand = new DatabaseCommand(dbCommand, provider.Object);
            var actual = dataCommand.GetDataSet(connection.Object, dataSetName, tables);

            // Assert
            var comparer = new DataSetEqualityComparer();
            Assert.IsTrue(comparer.Equals(ds, actual));
        }

        #endregion

        #region BeginTransaction Tests

        [TestMethod]
        public void BeginTransaction_CallsBeginTransactionOnTransactionManager()
        {
            // Arrange
            var commandText = "DELETE FROM Employee";
            var connection = CreateConnection();
            var provider = CreateProvider(commandText, connection.Object);
            var transactionManager = new TestTransactionManager();

            // Act
            var dataCommand = new DatabaseCommand(new TestDbCommand(commandText), provider.Object, transactionManager,
                                                  new IDbDataParameter[] {});
            dataCommand.BeginTransaction();

            // Assert
            Assert.AreEqual(1, transactionManager.BeginCalledCount);
        }

        #endregion

        #region CompleteTransaction Tests

        [TestMethod]
        public void CompleteTransaction_CallsCompleteTransactionOnTransactionManager()
        {
            // Arrange
            var commandText = "DELETE FROM Employee";
            var connection = CreateConnection();
            var provider = CreateProvider(commandText, connection.Object);
            var transactionManager = new TestTransactionManager();

            // Act
            var dataCommand = new DatabaseCommand(new TestDbCommand(commandText), provider.Object, transactionManager,
                                                  new IDbDataParameter[] {});
            dataCommand.BeginTransaction();
            dataCommand.CompleteTransaction();

            // Assert
            Assert.AreEqual(1, transactionManager.CompleteCalledCount);
        }

        #endregion

        #region AbortTransaction Tests

        [TestMethod]
        public void AbortTransaction_CallsAbortTransactionOnTransactionManager()
        {
            // Arrange
            var commandText = "DELETE FROM Employee";
            var connection = CreateConnection();
            var provider = CreateProvider(commandText, connection.Object);
            var transactionManager = new TestTransactionManager();

            // Act
            var dataCommand = new DatabaseCommand(new TestDbCommand(commandText), provider.Object, transactionManager,
                                                  new IDbDataParameter[] {});
            dataCommand.BeginTransaction();
            dataCommand.AbortTransaction();

            // Assert
            Assert.AreEqual(1, transactionManager.AbortCalledCount);
        }

        #endregion

        #region SetParameters Tests

        [TestMethod]
        public void SetParameters_EmptyParametersArray_DoesNotThrowException()
        {
            // Arrange
            var commandText = "SELECT * FROM Employee";
            var connection = CreateConnection();
            var provider = CreateProvider(commandText, connection.Object);

            // Act
            var dataCommand = new DatabaseCommand(commandText, provider.Object);
            dataCommand.SetParameters(new object[]{});

        }
        #endregion

        #region Private Methods

        private DataSet GetEmployeeSet(string dataSetName, IEnumerable<string> tables)
        {
            var ds = new DataSet(dataSetName);
            foreach (var table in tables)
            {
                ds.Tables.Add(table);
            }
            return ds;
        }

        private DataTable GetTable(string tableName)
        {
            var dt = new DataTable(tableName);
            switch (tableName.ToLower())
            {
                case "employee":
                    dt.Columns.Add("Name");
                    dt.Columns.Add("Address");
                    dt.Columns.Add("City");
                    dt.Rows.Add("Emp 1", "123 Main Street", "Franklin");
                    dt.Rows.Add("Emp 2", "222 Broadway", "Nashville");
                    break;
                case "job":
                    dt.Columns.Add("JobTitleId");
                    dt.Columns.Add("JobTitle");
                    dt.Rows.Add("1", "Job 1");
                    dt.Rows.Add("2", "Job 2");
                    break;
            }
            return dt;
        }

        #endregion
    }
}